<?php
/**
 * 
 * 可视化模板解析类
 *
 * {$}	如{$a}{$a.b.c}					=>变量<?=$a?> <?=$a['b']['c']?>
 * {=}	如{=date('Y-m-d')}				=>语法或常量调用
 *
 */
class View_Parser
{
	/**
	 * 存放临时串
	 * @var array
	 */
	protected static $stack;

	protected $options = array(
			'res_dir_prefix' => 'res' ,
			'res_base_url' => NULL ,
			'with_js_version' => TRUE ,
			'with_css_version' => TRUE ,
			'with_images_version' => TRUE ,
			'clear_comments' => FALSE ,
			'clear_blanks' => FALSE ,
			'tidy_css' => FALSE
	);

	public function __construct (array $options)
	{
		$this->options = array_merge($this->options, $options);
	}
	public function readfile($file)
	{
		ob_start();
		try{
			readfile($file,TRUE);
		}
		catch (Exception $e)
		{
			return "<p>The template {$file} not fount.</p>";
		}
		return ob_get_clean();
	}

	/**
	 * 编译模板文件
	 *
	 * @param string $tpl 要编译模板文件名
	 * @param string $cpl 编译到的目标,如果为TRUE,直接输出显示结果
	 * @param array $data assign到当前模板的变量
	 * @param boolean $combine 是否合并子模板到主模板
	 * @return mixed	编译成功,返回编译后的结果
	 */
	public function compile ($tpl_file, $cpl_dir = NULL, $data = array())
	{
		$templates=$this->readfile($tpl_file);
		if(!$templates)return FALSE;
		$options = $this->options;
		$eval = ($cpl_dir === TRUE) ? TRUE : FALSE;
		$templates = $this->parse($templates, $tpl_file, $data, $eval);
		$cpl_file="{$cpl_dir}/{$tpl_file}";
		$dir=dirname($cpl_file);
		if(!is_dir($dir))mkdir($dir,0777,TRUE);
		$hd =file_put_contents($cpl_file, $templates);
		if (! $hd) exit("Could not save the compiled template file:'{$cpl_file}'.");
		return $templates;
	}

	/**
	 * 解析模板内容
	 *
	 * @param string $templates 模板文件的内容
	 * @param string $tpl_file 当前模板文件名称，用于解析子模板
	 * @param array $data 到当前模板的变量
	 * @param boolean $combine 是否合并子模板到主模板
	 * @return void	不返回值
	 */
	public function parse ($templates, $tpl_file, $data = array(), $eval_code = FALSE)
	{
		$options = $this->options;
		if (! $templates) return '';

		//加载子模板二次(只能嵌套二层)
		if ($tpl_file)
		{
			$templates = self::parse_sub_tpl($templates, $data);
			$templates = self::parse_sub_tpl($templates, $data);
		}

		//清理注释
		if ($options['clear_comments'])
		{
			$templates = self::clear_comments($templates);
		}

		//修正css文件中的位置
		if ($options['tidy_css'])
		{
			$templates = self::fix_css($templates);
		}

		//清理空白
		if ($options['clear_blanks'])
		{
			$templates = self::clear_blank($templates);
		}

		//解析if,loop语法
		$templates = self::parse_grammar($templates);

		//解析变量类表达式语法{}
		$templates = self::parse_var($templates);

		//解析misc
		$templates = $this->parse_misc($templates);

		//生成块缓存
		$templates = $this->parse_block($templates, $data);

		if ($eval_code)
		{
			$templates=$this->eval_html($templates, $data);
		}
		return $templates;
	}

	public static function eval_html ($__html__, $__data__)
	{
		extract($__data__, EXTR_SKIP);
		ob_start();
		eval("?>{$__html__}");
		return ob_get_clean();
	}

	/**
	 * 清理所有空白
	 *
	 * @param string $templates
	 */
	public static function clear_blank ($templates)
	{
		$templates = preg_replace(array(
				"/([\s]{2,})/" ,
				"/(>[\s]+<)/"
		), array(
				' ' ,
				'><'
		), $templates);
		return $templates;
	}

	/**
	 * 生成缓存块
	 *
	 * @param string $templates
	 * @param array $data
	 * @return string
	 */
	public static function parse_block ($templates, $data)
	{
		if (is_array($data)) extract($data, EXTR_SKIP);
		$var = null;
		if (preg_match_all('/<\?=cache\?>(.*?)<\?=\/cache\?>|<!--{cache}-->(.*?)<!--{\/cache}-->/si', $templates, $var))
		{
			if (isset($var[1]) && isset($var[2]))
			{
				$rpm = '';
				foreach ($var[1] as $key => &$val)
				{
					if (! $val && $var[2][$key])
					{
						$val = &$var[2][$key];
					}
					$path = R::get('cfg.path.caches');
					$tmpfname = tempnam($path, "etpl_tmp_");
					file_put_contents($tmpfname, $val);
					ob_start();
					include ($tmpfname);
					$rpm = ob_get_clean();
					unlink($tmpfname);
					$templates = str_replace($var[0][$key], $rpm, $templates);
				}
			}
		}
		return $templates;
	}

	/**
	 * 清理<!--???-->HTML注释
	 */
	public static function clear_comments ($templates)
	{
		$var = null;
		if (preg_match_all('/(<style type="text\/css">|<script type="text\/javascript">)?([\s]*(\r\n)*[\s]*)?(<!--[^{#].*?[\s]*-->)/si', $templates, $var))
		{
			if (isset($var[1]) && is_array($var[1]))
			{
				foreach ($var[1] as $key => &$val)
				{
					$val = trim($val);
					if (! $val)
					{
						$templates = str_replace($var[4][$key], '', $templates);
					}
				}
			}
		}
		return $templates;
	}

	/**
	 * 解析if(),elseif()语句
	 * <code>
	 * <!--{ if !empty(qihoo_status.bb.cc) && searchbo>=xstatus && aaa(date,time,second)!=4 || (bbb>=6) && bbb<100 && aaa(date)<100 && aaa!=90}-->
	 * 被解析为
	 * <?php
	 * if(!empty($qihoo_status['bb']['cc']) && $searchbo>=$xstatus && aaa($date,$time,$second)!=4 || ($bbb>=6) && $bbb<100 && aaa($date)<100 && $aaa!=90)
	 	* {
	 * ?>
	 * </code>
	 */
	public static function parse_if ($str = '', $if_else = 'if')
	{
		$var = null;
		if (preg_match_all('/([a-z_][a-z0-9._:()]*)/i', trim($str), $var))
		{
			$str = self::parse_exprssion($str);
			if ($if_else === 'if')
			{
				return "<?php if(" . $str . "):?>";
			}
			else
			{
				return "<?php elseif(" . $str . "):?>";
			}
		}
		else
		{
			return false;
		}
	}

	public static function parse_loop ($str = null)
	{
		static $i = 0;
		$str = preg_replace('/(\s)+/', ' ', $str);
		$rtn = '';
		$counter = '$i';
		if ($str)
		{
			if (strpos($str, ' '))
			{
				$arr = explode(' ', trim($str));
				$var = self::parse_express_name($arr[0]);
				$cnt = count($arr);
				if ($cnt === 4)
				{
					$counter = $arr[3];
					$rtn = $var . ' as ' . $arr[1] . '=>' . $arr[2];
				}
				elseif ($cnt == 3)
				{
					$rtn = $var . ' as ' . $arr[1] . '=>' . $arr[2];
				}
				elseif ($cnt == 2)
				{
					$rtn = $var . ' as ' . $arr[1];
				}
				else
				{
					return false;
				}
			}
			else
			{
				$var = self::parse_express_name($str);
				$rtn = '(' . $var . ' as $value){';
			}
			$str = "<?php\n\tsettype(" . $var . ",'array');\n\t$counter=-1;\n\tforeach(" . $rtn . '):' . "\n\t\t$counter++\n?>";
			$i ++;
			return $str;
		}
		else
		{
			return false;
		}
	}

	public static function parse_exprssion ($str = '')
	{
		$var = null;
		if (preg_match_all('/\$[a-z_][a-z0-9_]*([.][a-z0-9_]+)*/i', trim($str), $var))
		{
			$rpc = self::parse_var_more($var[0]);
			$str = str_replace($var[0], $rpc, $str);
		}
		return $str;
	}

	public static function parse_var_more ($arr)
	{
		if (is_array($arr) && $arr)
		{
			foreach ($arr as $key => $value)
			{
				$arr[$key] = self::parse_express_name($value);
			}
			return $arr;
		}
		else
		{
			return false;
		}
	}

	public static function parse_express_name ($tag)
	{
		if (strpos($tag, '.'))
		{
			list ($parent, $tag) = explode('.', $tag, 2);
			$obj = $parent;
			while (is_int(strpos($tag, '.')))
			{
				list ($parent, $tag) = explode('.', $tag, 2);
				if (is_numeric($parent))
				{
					$obj .= "[{$parent}]";
				}
				else
				{
					$obj .= "['$parent']";
				}
			}
			if (! is_numeric($tag))
			{
				$obj .= "['{$tag}']";
			}
			else
			{
				$obj .= "[{$tag}]";
			}
		}
		else
		{
			$obj = ($tag) ? $tag : '';
		}
		return $obj;
	}

	/**
	 * 修改模板中css到head中
	 *
	 * @param string $templates
	 * @return boolean
	 */
	public static function fix_css (&$templates)
	{
		//取出link css
		$links = null;
		if (preg_match_all('/<link.*?>/si', $templates, $links))
		{
			if (isset($links[0]) and is_array($links[0]))
			{
				foreach ($links[0] as $val)
				{
					$templates = str_replace($val, '', $templates);
				}
				$links = implode("\n", $links[0]) . "\n";
			}
		}
		else
		{
			$links = null;
		}
		//取出样式及style
		$styles = NULL;
		if (preg_match_all('/<style.*?>(.*?)<\/style>/si', $templates, $styles))
		{
			if (isset($styles[0]) and is_array($styles[0]))
			{
				foreach ($styles[0] as $val)
				{
					$templates = str_replace($val, '', $templates);
				}
			}
			$styles = "<style type=\"text/css\">" . implode("\n", $styles[1]) . "</style>\n";
		}
		else
		{
			$styles = NULL;
		}
		//取出head,如果没有，以模板首部为文件头
		$hd = null;
		$head = null;
		$rs = preg_match('/<head>(.*?)<\/head>/si', $templates, $hd);
		if ($rs && isset($hd[1]))
		{
			$templates = preg_replace("/<\/head>/i", "{$links}{$styles}\n</head>", $templates);
		}
		else
		{
			$templates = "{$links}{$styles}\n{$templates}";
		}
		return $templates;
	}

	/**
	 * 如果存在子模板则进行替换,子模板格式为:<!--#include file="module/action.top" -->
	 *
	 * @param string $templates
	 * @param string $tpl_dir
	 * @param array $data
	 * @return string
	 */
	public function parse_sub_tpl ($templates, $data = array())
	{
		$rs=preg_match_all('/<!--#include[\s]+file="([a-z0-9?\\/._-]+)".*?-->/ie',$templates,$matches);
		if(!empty($matches[0]))
		{
			foreach($matches[0] as $k=>$rpc)
				$templates=str_replace($rpc,$this->readfile($matches[1][$k]),$templates);
		}
		$rs=preg_match_all("/<\?php\s*include\s*\(?'([^;$]+?)'\)?;\s*\?>/is",$templates,$matches);
		if(!empty($matches[0]))
		{
			foreach($matches[0] as $k=>$rpc)
				$templates=str_replace($rpc,$this->readfile($matches[1][$k]),$templates);
		}
		$rs=preg_match_all("/<\?php\s*include\s*\(?\"([^;$]+?)\"\)?;\s*\?>/is",$templates,$matches);
		if(!empty($matches[0]))
		{
			foreach($matches[0] as $k=>$rpc)
				$templates=str_replace($rpc,$this->readfile($matches[1][$k]),$templates);
		}
		return $templates;
	}

	/**
	 * 取子模板内容
	 *
	 * @param string $tpl_dir
	 * @param string $file
	 * @return string
	 */
	public static function get_sub_tpl ($file)
	{
		return $
		$file = self::join_path($tpl_dir, $file);
		if (is_file($file))
		{
			return file_get_contents($file); //读取子模板内容
		}
		else
		{
			return "<h1>Parser Error: subtemplate not found ({$file}).</h1>";
		}
	}

	/**
	 * 连接路径
	 *
	 * @param string $dir
	 * @param string $name
	 * @return string
	 */
	public static function join_path ($dir, $name)
	{
		return rtrim($dir, '\\/') . "/{$name}";
	}

	/**
	 * 解析模板语法if/else/loop
	 */
	public static function parse_grammar ($templates)
	{
		if (! $templates)
		{
			return $templates;
		}
		//替换<!--{else}--><!--{/loop}--><!--{/if}-->
		$pattern = array(
				'<!--{else}-->' ,
				'<!--{/if}-->' ,
				'<!--{/endif}-->' ,
				'<!--{/loop}-->'
		);
		$rpc = array(
				'<?php else:?>' ,
				'<?php endif;?>' ,
				'<?php endif;?>' ,
				'<?php endforeach;?>'
		);
		$templates = str_replace($pattern, $rpc, $templates);
		//替换:<!--{loop $array $key=>$value}--><!--{if expresion}--><!--{elseif expresion}-->
		$callback = create_function('$matches', 'if (preg_match(\'/^(if|elseif|loop)[\s]*/i\', $matches[1], $match))
		{
				$arg = substr($matches[1], strlen($match[0]));
				if ($match[1] == \'loop\')
				{
				$rpc = View_Parser::parse_loop($arg);
	}
				elseif ($match[1] == \'if\' || $match[1] == \'elseif\')
				{
				$rpc = View_Parser::parse_if($arg, $match[1]);
	}
				else
				{
				$rpc = $match[0];
	}
				return $rpc;
	}
				else
				{
				return "<?=" . View_Parser::parse_exprssion($matches[1]) . "?>";
	}');
		$templates = preg_replace_callback('/<!--\{[\s]*(.+?)}-->/', $callback, $templates);
		return $templates;
	}

	/**
	 * 替换变量及语言,变量为{varname.name}
	 */
	public static function parse_var ($templates)
	{
		//变量
		$templates = preg_replace('/<\{([$].{1,50}?)\}>/e', "'<?='.self::parse_exprssion('\${1}').'?>'", $templates);

		//常量及函数
		$templates = preg_replace("/<{=(.{1,250})}>/e", "'<?='.\${1}.'?>'", $templates);
		return $templates;
	}

	/**
	 * 更换misc资源为绝对路径
	 */
	public function parse_misc ($templates)
	{
		$res_dir_prefix = ! empty($this->options['res_dir_prefix']) ? $this->options['res_dir_prefix'] : 'res';
		$pattern = array(
				"|(?<=url\()(\.\.)*/{$res_dir_prefix}/(.*?)(?=\))|e" ,
				"|(?<=\")(\.\.)*/{$res_dir_prefix}/(.*?)(?=\")|e" ,
				"|(?<=\')(\.\.)*/{$res_dir_prefix}/(.*?)(?=')|e"
				);
		/* foreach($pattern as $p)
		{
			$rs=preg_match_all($p, $templates, $matches);
		} */
		
		$templates = preg_replace($pattern, "\$this->parse_misc_callback('\\2')", $templates);
		return $templates;
	}

	/**
	 * 解析misc回调方法
	 *
	 * @param strinc $src
	 * @return string
	 */
	public function parse_misc_callback ($src)
	{
		$options=$this->options;
		$res_dir_prefix = ! empty($options['res_dir_prefix']) ? $options['res_dir_prefix'] : 'res';
		$hosts = $options['res_base_url'];
		$counter = count($hosts);
		switch ($counter)
		{
			case 0:
				$host='';
				break;
			case 1:
				$host=current($hosts);
				break;
			default:
				$n = abs(crc32($src)) % $counter;
				$host=$hosts[$n];
		}

		$src = "{$host}{$src}";
		return $src;
	}
}
